<?php

// @formatter:off
/**
 * @file WCTimer.php
 * @author Alejandro Dario Simi
 * @date $Date: 2014-05-04 01:33:52 +0000 (Sun, 04 May 2014) $
 *
 * $Id: WCTimer.php 111 2014-05-04 01:33:52Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/trunk/includes/WCTimer.php $
 */
// @formatter:on

/**
 * @class WCTimer This singleton class holds the logic to manage timers and impact
 * them into a database table.
 */
class WCTimer extends WCSingleton {
	/**
	 * @var int This is a stop-watch for global timing.
	 */
	protected $_globalTimer = 0;
	/**
	 * @var boolean This flag indicates if global stop-watch is running.
	 */
	protected $_globalTimerRunning = false;
	/**
	 * @var boolean This flag indicates if current execution is being done
	 * from shell or from web.
	 */
	protected $_onShell = false;
	/**
	 * @var int[string] This is a list of stop-watches associated with an
	 * identifier.
	 */
	protected $_timers = array();
	/**
	 * @var int[string] This is a list of flags associated with an identifier
	 * used to know if a stop-watch is running.
	 */
	protected $_timersRunning = array();
	/**
	 * Class constructor.
	 */
	protected function __construct() {
		//
		// Is it a shell execution?
		$this->_onShell = defined("__SHELL__");
	}
	//
	// Public methods.
	/**
	 * This method removes all time statistics stored in database.
	 */
	public function clearStats() {
		//
		// Loading global pointers
		global $wcDefaults;
		//
		// Checking if there's a time limit for statistics.
		if($wcDefaults["stats-max-time"] > 0) {
			//
			// Loading global pointers
			global $wcDatabaseAccess;
			//
			// Creating database shortcuts.
			// @{
			$db = WCConnection::Instance();
			$dbprefix = $wcDatabaseAccess["prefix"];
			// @}
			//
			// Creating a statement to remove old entries.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($db->has($stmtId)) {
				$stmt = $db->get($stmtId);
			} else {
				$query = "delete from {$dbprefix}stats_timer\n";
				$query .= "where       st_t_sdate < :sdate\n";

				$stmt = $db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(":sdate" => date("Ymd", strtotime("-{$wcDefaults["stats-max-time"]} day", time())));
			//
			// Removing old entries.
			$stmt->execute($params);
		}
	}
	/**
	 * This method allows to know global stop-watch start position when it's
	 * running or its elapsed time when it's stopped.
	 *
	 * @warning if it never was started it triggers an exception.
	 *
	 * @param boolean $running When given it will be used to return global
	 * stop-watch status.
	 * @return int Returns an amount of milliseconds.
	 */
	public function globalTimer(&$running = null) {
		//
		// Setting a default value to be returned.
		$out = -1;
		//
		// Checking if global timer was used at least once.
		if($this->_globalTimer) {
			//
			// Getting start position.
			$out = ceil($this->_globalTimer * 1000);
			//
			// Returning current status.
			$running = $this->_globalTimerRunning;
		} else {
			//
			// Triggering exception.
			trigger_error(__CLASS__."::".__FUNCTION__."(): Global timer was never used", E_USER_ERROR);
		}
		//
		// Returning current position.
		return $out;
	}
	/**
	 * This method stores global stop-watch results in database for further
	 * statistics analysis.
	 *
	 * @warning it stops global stop-watch.
	 *
	 * @param string $caller @todo
	 */
	public function saveGlobal($caller = "no-caller") {
		//
		// Loading global pointers
		global $wcDefaults;
		//
		// Stopping stop-watch.
		$this->stopGlobal();
		//
		// When there's not time limit for statistics, none is stored.
		if($wcDefaults["stats-max-time"] > 0) {
			//
			// Clearing old entries.
			$this->clearStats();
			//
			// Loading global pointers
			global $wcDatabaseAccess;
			//
			// Creating database shortcuts.
			// @{
			$db = WCConnection::Instance();
			$dbprefix = $wcDatabaseAccess["prefix"];
			// @}
			//
			// Creating a statement to insert a stop-watch's
			// statistics or update it if it already exists.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($db->has($stmtId)) {
				$stmt = $db->get($stmtId);
			} else {
				$query = "insert\n";
				$query .= "        into {$dbprefix}stats_timer (\n";
				$query .= "                st_t_code, st_t_action, st_t_caller, st_t_group, st_t_collection,\n";
				$query .= "                st_t_tag, st_t_directory, st_t_comic, st_t_rule, st_t_maximum,\n";
				$query .= "                st_t_minimum, st_t_average, st_t_times, st_t_method, st_t_sdate)\n";
				$query .= "        values (:code, :action, :caller, :group, :collection,\n";
				$query .= "                :tag, :directory, :comic, :rule, :time,\n";
				$query .= "                :time, :time, 1, :method, date_format(now(),'%Y%m%d'))\n";
				$query .= "        on duplicate key\n";
				$query .= "                update  st_t_maximum = greatest(st_t_maximum, :time),\n";
				$query .= "                        st_t_minimum = least(st_t_minimum, :time),\n";
				$query .= "                        st_t_average = round((st_t_average + :time) / 2),\n";
				$query .= "                        st_t_times   = st_t_times + 1\n";

				$stmt = $db->prepare($stmtId, $query);
			}
			// @}
			//
			// Loading global pointers
			global $wcAction;
			global $wcGroup;
			global $wcCollection;
			global $wcTag;
			global $wcDirectory;
			global $wcComic;
			global $wcRule;
			//
			// Setting a default code to identify current entry
			// generator.
			$code = "UNKNOWN";
			//
			// Identifier depends on the environment (shell or web).
			if(!$this->_onShell) {
				$code = "{$_SERVER["SCRIPT_NAME"]}?{$_SERVER["QUERY_STRING"]}";
			} else {
				$code = $_SERVER["SCRIPT_NAME"];
			}
			//
			// Building database parameters.
			$params = array(
				":code" => $code,
				":action" => $wcAction ? $wcAction : "UNKNOWN",
				":caller" => $caller,
				":group" => $wcGroup ? $wcGroup->id : null,
				":collection" => $wcCollection ? $wcCollection->id : null,
				":tag" => $wcTag ? $wcTag->id : null,
				":directory" => $wcDirectory ? $wcDirectory->id : null,
				":comic" => $wcComic ? $wcComic->id : null,
				":rule" => $wcRule ? $wcRule->id : null,
				":time" => $this->globalTimer(),
				":method" => isset($_SERVER["REQUEST_METHOD"]) ? $_SERVER["REQUEST_METHOD"] : ""
			);
			//
			// Storing timer.
			$stmt->execute($params);
		}
	}
	/**
	 * This method stores a specific stop-watch results in database for
	 * further statistics analysis.
	 *
	 * @warning it stops global stop-watch.
	 *
	 * @param string $id Stop-watch identifier.
	 * @param string $caller @todo
	 * @param string $code Code to identify this group of statistics entries.
	 */
	public function save($id, $caller = "no-caller", $code = "UNKNOWN") {
		//
		// Checking if this timer exists and it's running.
		if(isset($this->_timersRunning[$id]) && $this->_timersRunning[$id]) {
			//
			// Loading global pointers
			global $wcDefaults;
			//
			// Stopping stop-watch.
			$this->stop($id);
			//
			// When there's not time limit for statistics, none is
			// stored.
			if($wcDefaults["stats-max-time"] > 0) {
				//
				// Clearing old entries.
				$this->clearStats();
				//
				// Loading global pointers
				global $wcDatabaseAccess;
				//
				// Creating database shortcuts.
				// @{
				$db = WCConnection::Instance();
				$dbprefix = $wcDatabaseAccess["prefix"];
				// @}
				//
				// Creating a statement to insert a stop-watch's
				// statistics or update it if it
				// already exists.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($db->has($stmtId)) {
					$stmt = $db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$dbprefix}stats_timer (\n";
					$query .= "                st_t_code, st_t_action, st_t_caller, st_t_group, st_t_collection,\n";
					$query .= "                st_t_tag, st_t_directory, st_t_comic, st_t_rule, st_t_maximum,\n";
					$query .= "                st_t_minimum, st_t_average, st_t_times, st_t_method, st_t_sdate)\n";
					$query .= "        values (:code, :action, :caller, :group, :collection,\n";
					$query .= "                :tag, :directory, :comic, :rule, :time,\n";
					$query .= "                :time, :time, 1, :method, date_format(now(),'%Y%m%d'))\n";
					$query .= "        on duplicate key\n";
					$query .= "                update  st_t_maximum = greatest(st_t_maximum, :time),\n";
					$query .= "                        st_t_minimum = least(st_t_minimum, :time),\n";
					$query .= "                        st_t_average = round((st_t_average + :time) / 2),\n";
					$query .= "                        st_t_times   = st_t_times + 1\n";

					$stmt = $db->prepare($stmtId, $query);
				}
				// @}
				//
				// Loading global pointers
				global $wcAction;
				global $wcGroup;
				global $wcCollection;
				global $wcTag;
				global $wcDirectory;
				global $wcComic;
				global $wcRule;

				//
				// Identifier depends on the environment (web or
				// cron).
				if(!$this->_onShell) {
					$code = "{$_SERVER["SCRIPT_NAME"]}?{$_SERVER["QUERY_STRING"]}";
				} elseif(!defined("__CRON__")) {
					$code = $_SERVER["SCRIPT_NAME"];
				}
				//
				// Building database parameters.
				$params = array(
					":code" => $code,
					":action" => $wcAction ? $wcAction : "UNKNOWN",
					":caller" => $caller,
					":group" => $wcGroup ? $wcGroup->id : null,
					":collection" => $wcCollection ? $wcCollection->id : null,
					":tag" => $wcTag ? $wcTag->id : null,
					":directory" => $wcDirectory ? $wcDirectory->id : null,
					":comic" => $wcComic ? $wcComic->id : null,
					":rule" => $wcRule ? $wcRule->id : null,
					":time" => $this->timer($id),
					":method" => isset($_SERVER["REQUEST_METHOD"]) ? $_SERVER["REQUEST_METHOD"] : ""
				);
				//
				// Storing timer.
				$stmt->execute($params);
			}
		} else {
			//
			// Triggering exception.
			trigger_error(__CLASS__."::".__FUNCTION__."(): Timer '{$id}' in already running", E_USER_ERROR);
		}
	}
	/**
	 * This method starts a specific stop-watch.
	 *
	 * @warning if a stop watch is already running, it will trigger an
	 * exception.
	 *
	 * @param string $id Stop-watch identifier.
	 */
	public function start($id) {
		//
		// Checking timer is not running.
		if(!isset($this->_timersRunning[$id]) || !$this->_timersRunning[$id]) {
			//
			// Setting stop-watch as running
			$this->_timersRunning[$id] = true;
			//
			// Setting start position.
			$this->_timers[$id] = microtime(true);
		} else {
			//
			// Triggering exception.
			trigger_error(__CLASS__."::".__FUNCTION__."(): Timer '{$id}' in already running", E_USER_ERROR);
		}
	}
	/**
	 * This method starts global stop-watch.
	 *
	 * @warning if a stop watch is already running, it will trigger an
	 * exception.
	 */
	public function startGlobal() {
		//
		// Checking global timer is not running.
		if(!$this->_globalTimerRunning) {
			//
			// Setting stop-watch as running
			$this->_globalTimerRunning = true;
			//
			// Setting start position.
			$this->_globalTimer = microtime(true);
		} else {
			//
			// Triggering exception.
			trigger_error(__CLASS__."::".__FUNCTION__."(): Global timer already started", E_USER_ERROR);
		}
	}
	/**
	 * This method allows to stop and know a specific stop-watch count.
	 *
	 * @warning if it's not running ot it doesn't exists, it triggers an
	 * exception.
	 *
	 * @param string $id Stop-watch identifier.
	 * @return int Returns an amount of milliseconds.
	 */
	public function stop($id) {
		//
		// Checking stop-watch exists and it's running.
		if(isset($this->_timersRunning[$id]) && $this->_timersRunning[$id]) {
			//
			// Setting stop-watch as not running.
			$this->_timersRunning[$id] = false;
			//
			// Calculating and setting elapsed time.
			$this->_timers[$id] = microtime(true) - $this->_timers[$id];
		} elseif(!isset($this->_timersRunning[$id])) {
			//
			// Triggering exception if it doesn't exists.
			trigger_error(__CLASS__."::".__FUNCTION__."(): Timer '{$id}' was never started", E_USER_ERROR);
		} else {
			//
			// Triggering exception if it's not running.
			trigger_error(__CLASS__."::".__FUNCTION__."(): Timer '{$id}' in not running", E_USER_ERROR);
		}
		//
		// Returning final count.
		return $this->timer($id);
	}
	/**
	 * This method allows to stop and know global stop-watch count.
	 *
	 * @warning if it's not running it triggers an exception.
	 *
	 * @return int Returns an amount of milliseconds.
	 */
	public function stopGlobal() {
		//
		// Checking global stop-watch is running.
		if($this->_globalTimerRunning) {
			//
			// Setting stop-watch as not running.
			$this->_globalTimerRunning = false;
			//
			// Calculating and setting elapsed time.
			$this->_globalTimer = microtime(true) - $this->_globalTimer;
		} else {
			//
			// Triggering exception.
			trigger_error(__CLASS__."::".__FUNCTION__."(): Global timer is not started", E_USER_ERROR);
		}
		//
		// Returning final count.
		return $this->globalTimer($id);
	}
	/**
	 * This method allows to know a specific stop-watch start position when
	 * it's running or its elapsed time when it's stopped.
	 *
	 * @param string $id Stop-watch identifier.
	 * @param boolean $running When given it will be used to return global
	 * stop-watch status.
	 * @return int Returns an amount of milliseconds.
	 */
	public function timer($id, &$running = null) {
		//
		// Setting default values to be returned.
		// @{
		$out = 0;
		$running = null;
		// @}
		//
		// Checking if timer exists.
		if(isset($this->_timersRunning[$id])) {
			//
			// Getting start position.
			$out = ceil($this->_timers[$id] * 1000);
			//
			// Returning current status.
			$running = $this->_timersRunning[$id];
		}
		//
		// Returning current position.
		return $out;
	}
}
